package net.pimpas.scanner;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import net.pimpas.core.scanner.Scanner;
import net.pimpas.core.scanner.exception.ScannerException;
import net.pimpas.core.visitor.ClassVisitor;
import net.pimpas.core.visitor.DirectoryVisitor;
import net.pimpas.core.visitor.FileVisitor;
import net.pimpas.core.visitor.PackageVisitor;
import net.pimpas.core.visitor.Visitor;
import net.pimpas.framework.PimpasFramework;
import net.pimpas.util.reflection.ReflectionUtils;

public class ClassPathScanner implements Scanner<Object> {
	
	private static final String RIGHT_SLASH			= "/";
	private static final String DOT					= ".";
	private static final String PATH_SEPARATOR		= System.getProperty("path.separator");
	private static final String FILE_SEPARATOR		= System.getProperty("file.separator");
	private static final String SPACE				= "%20";
	private static final String SIMPLE_SPACE		= " ";
	private static final String CLASS_EXTENSION		= ".class";
	private static final String JAR_EXTENSION   	= ".jar";
	private static final List<String> IGNORE_JARS 	= new ArrayList<String>();
	private final HashSet<Package> readPackages 	= new HashSet<Package>();
	
	public static void addIgnoreJar(String regex) {
		for(int i=0;i<IGNORE_JARS.size();i++)
			if(IGNORE_JARS.get(i).equals(regex)) return;
		
		IGNORE_JARS.add(regex);
	}
	
	public String getName() {
		return this.getClass().getName();
	}

	public List<Object> getResults() {
		return null;
	}

	public void scan() throws ScannerException {
		String classPath = System.getProperty("java.class.path");
		URL base = getClass().getClassLoader().getResource("/../lib/");
		if(base != null) classPath += PATH_SEPARATOR+base.getPath().replaceAll(SPACE, SIMPLE_SPACE);
		base = getClass().getClassLoader().getResource("/../classes/");
		if(base != null) classPath += PATH_SEPARATOR+base.getPath().replaceAll(SPACE, SIMPLE_SPACE);
		File f = null;
		String[] paths = classPath.split(PATH_SEPARATOR);
		String emptyString = "";
		for(int i=0;i<paths.length;i++,f=null) {
			// jar file, directory or simple java class
			f = new File(paths[i]);
			if(f.exists() && f.isDirectory()) {
				// directory
				try {
					this.runDirectory( emptyString, f );
				} catch (Exception e) {
					throw new ScannerException(e);
				}
			} else {
				// check jar file
				if(f.getName().endsWith(JAR_EXTENSION)) {
					// yes, jar file
					try {
						this.runJarFile( f );
					} catch (URISyntaxException e) {
						throw new ScannerException(e);
					}
				} else
					if(this.isClassFile(f.getName()))
						this.runClass(f.getPath(), f.getName());
			}
		}
		
		paths = null;
		readPackages.clear();
		System.gc();
	}
	
	private void alertClassVisitor(Class<?> clazz) {
		ClassVisitor cv = null;
		for(Visitor v: PimpasFramework.getInstance().getVisitors()) {
			if(ReflectionUtils.isSubclass(v.getClass(), ClassVisitor.class)) {
				cv = (ClassVisitor) v;
				if(cv.accept(clazz))
					cv.visitClass(clazz);
			}
		}
	}

	
	private void alertFileVisitor(URL file) {
		FileVisitor fv = null;
		for(Visitor v: PimpasFramework.getInstance().getVisitors()) {
			if(ReflectionUtils.isSubclass(v.getClass(), FileVisitor.class)) {
				fv = (FileVisitor) v;
				if(fv.accept(file))
					fv.visitFile(file);
			}
		}
	}
	
	private void alertDirectoryVisitor(URL file) {
		DirectoryVisitor dv = null;
		for(Visitor v: PimpasFramework.getInstance().getVisitors()) {
			if(ReflectionUtils.isSubclass(v.getClass(), DirectoryVisitor.class)) {
				dv = (DirectoryVisitor) v;
				if(dv.accept(file))
					dv.visitDirectory(file);
			}
		}
	}
	
	private void alertPackageVisitor(Package pkg) {
		PackageVisitor pv = null;
		if(this.readPackages.contains(pkg)) return;
		
		for(Visitor v: PimpasFramework.getInstance().getVisitors()) {
			if(ReflectionUtils.isSubclass(v.getClass(), PackageVisitor.class)) {
				pv = (PackageVisitor) v;
				if(pv.accept(pkg))
					pv.visitPackage(pkg);
			}
		}
		
		this.readPackages.add(pkg);
	}
	
	private void runClass(String path, String fileName) {
		if(path != null)
			if(fileName.indexOf(path) >= 0)
				fileName = fileName.substring(fileName.indexOf(path)+path.length()+1);

		String clazx = null;
		clazx = fileName.replaceAll("\\"+FILE_SEPARATOR, DOT).replaceAll(RIGHT_SLASH, DOT);
		clazx = clazx.substring(0,clazx.indexOf(CLASS_EXTENSION));
		Class<?> clazz = null;
		try {
			clazz = Class.forName(clazx, false, getClass().getClassLoader());
			// call package visitors
			this.runPackages(clazz.getPackage());
			// call class visitors
			this.alertClassVisitor(clazz);
		} catch(Throwable e) {
		}
	}
	
	private void runPackages(Package pkg) {
		this.alertPackageVisitor(pkg);
	}
	
	private String isJarDenied( File f ) {
		Pattern pattern = null;
		for(String ignore: IGNORE_JARS) {
			pattern = Pattern.compile(ignore, Pattern.CASE_INSENSITIVE);
			if(pattern.matcher(f.getName()).find())
				return ignore;
		}
		
		return null;
	}

	private void runJarFile(File f) throws URISyntaxException {
		// check denied jars
		//String regex = null;
		if(this.isJarDenied(f)!=null) {
			//logger.debug("[ClassPathScanner]: Skipping Denied Jar ["+f.getName()+"] with regex ["+regex+"]");
			return;
		}
		JarFile jar;
		try {
			jar = new JarFile(f);
			Enumeration<JarEntry> entrys = jar.entries();
			JarEntry entry = null;
			while(entrys.hasMoreElements()) {
				entry = entrys.nextElement();
				if(entry.isDirectory()) {
					this.alertDirectoryVisitor(this.getClass().getClassLoader().getResource(entry.getName()));
				} else
					// call file visitors
					this.alertFileVisitor(this.getClass().getClassLoader().getResource(entry.getName()));

				if(isClassFile(entry.getName()))
					this.runClass(null, entry.getName());
			}
		} catch (IOException e) {
			
		}
	}

	private boolean isClassFile(String fileName) {
		return fileName.endsWith(CLASS_EXTENSION);
	}

	private void runDirectory(String path, File f) throws MalformedURLException, URISyntaxException {
		if(f == null) return;
		if(f.isDirectory())
			this.alertDirectoryVisitor(f.toURI().toURL());
		else {
			// call visitors file
			this.getClass().getClassLoader().getResource(f.getAbsolutePath());
			this.alertFileVisitor(f.toURI().toURL());
		}

		File[] files = f.listFiles();
		if(files != null) {
			for(int i=0;i<files.length;i++)
				runDirectory(path+files[i].getName()+FILE_SEPARATOR, files[i]);
			files = null;
		}

		if(this.isClassFile(f.getName()))
			this.runClass(f.getPath(), path);
		if(f.getName().endsWith(JAR_EXTENSION))
			this.runJarFile(f);
	}
}